﻿/// <reference path="jquery-1.5.1.min.js" />

/***** ECMAScript 5 / Javascript 1.6 compat *****/
//https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter
if (!Array.prototype.filter) {
    Array.prototype.filter = function (fun /*, thisp */) {
        "use strict";

        if (this === void 0 || this === null)
            throw new TypeError();

        var t = Object(this);
        var len = t.length >>> 0;
        if (typeof fun !== "function")
            throw new TypeError();

        var res = [];
        var thisp = arguments[1];
        for (var i = 0; i < len; i++) {
            if (i in t) {
                var val = t[i]; // in case fun mutates this
                if (fun.call(thisp, val, i, t))
                    res.push(val);
            }
        }

        return res;
    };
}
if (!Array.prototype.map) {
    Array.prototype.map = function (fun /*, thisp */) {
        "use strict";

        if (this === void 0 || this === null)
            throw new TypeError();

        var t = Object(this);
        var len = t.length >>> 0;
        if (typeof fun !== "function")
            throw new TypeError();

        var res = new Array(len);
        var thisp = arguments[1];
        for (var i = 0; i < len; i++) {
            if (i in t)
                res[i] = fun.call(thisp, t[i], i, t);
        }

        return res;
    };
}
if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function (searchElement /*, fromIndex */) {
        "use strict";

        if (this === void 0 || this === null)
            throw new TypeError();

        var t = Object(this);
        var len = t.length >>> 0;
        if (len === 0)
            return -1;

        var n = 0;
        if (arguments.length > 0) {
            n = Number(arguments[1]);
            if (n !== n) // shortcut for verifying if it's NaN
                n = 0;
            else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0))
                n = (n > 0 || -1) * Math.floor(Math.abs(n));
        }

        if (n >= len)
            return -1;

        var k = n >= 0
          ? n
          : Math.max(len - Math.abs(n), 0);

        for (; k < len; k++) {
            if (k in t && t[k] === searchElement)
                return k;
        }
        return -1;
    };
}
if (!Array.prototype.every) {
    Array.prototype.every = function (fun /*, thisp */) {
        "use strict";

        if (this === void 0 || this === null)
            throw new TypeError();

        var t = Object(this);
        var len = t.length >>> 0;
        if (typeof fun !== "function")
            throw new TypeError();

        var thisp = arguments[1];
        for (var i = 0; i < len; i++) {
            if (i in t && !fun.call(thisp, t[i], i, t))
                return false;
        }

        return true;
    };
}
if (!Array.prototype.some) {
    Array.prototype.some = function (fun /*, thisp */) {
        "use strict";

        if (this === void 0 || this === null)
            throw new TypeError();

        var t = Object(this);
        var len = t.length >>> 0;
        if (typeof fun !== "function")
            throw new TypeError();

        var thisp = arguments[1];
        for (var i = 0; i < len; i++) {
            if (i in t && fun.call(thisp, t[i], i, t))
                return true;
        }

        return false;
    };
}

//to be used with Array.sort
//usage: array.sort(by("field")).reverse();
//this method mutates (side effects).
var by = function (what) {

    return function (a, b) {
        var $a, $b;
        var singleCompare = function (a, b) {
            if (a === b) {
                return 0;
            }
            else if (typeof a === typeof b) {//string or number
                return a < b ? -1 : 1;
            }
            else
                return typeof a < typeof b ? -1 : 1;
        }

        if (typeof a === 'object' && typeof b === 'object' && a && b) {
            if (typeof what == "function") {
                $a = what(a);
                $b = what(b);
            }
            else if (typeof what === "string") {  //string field name:
                $a = a[what];
                $b = b[what];
            }
            else { throw "must provide argument 'what' if 'by' is called for typeof 'object'"; }
        }
        else if (typeof a !== typeof b) { throw "type mismatch." }
        else {  //a, b are string or numeric.                
            $a = a;
            $b = b;
        }

        /*  the function returns Array of fields, in order, to compare.
        elements of Arrays are assumed to be primitive type.
        */
        if ($a instanceof Array && $b instanceof Array) {
            if ($a.length !== $b.length) { throw new Error("Cannot compare Arrays of unequal length."); }
            for (var i = 0, len = $a.length; i < len; i++) {
                if ($a[i] < $b[i]) { return -1; }
                if ($a[i] > $b[i]) { return 1; }
            }
            return 0;
        }
        else {
            return singleCompare($a, $b);
        }

    };
};


//TODO: re-implement, and possibly mutable
Array.prototype.distinct = function () {    //isn't a mutator (C# LINQ, but not javascript behavior)
    var set = [];
    for (var i = 0; i < this.length; i++) {
        var element = this[i];
        if (!set.contains(element)) {
            set.push(element);
        }
    }
    return set;
}
Array.prototype.sum = function (getField) {
    var sum = 0, i = 0, 
        isFunction = typeof getField === "function";

    if (this.length === 0) {
        return 0;
    }
    var elementType = typeof this[0];
    if (!isFunction && elementType  !== "string" && elementType !== "number") {
        throw new TypeError("getField argument must be function or element type must be number or string.");
    }

    for (i = 0; i < this.length; i++) {
        var value = 0;
        if (isFunction) {
            value = getField(this[i]);
        }
        else if (elementType === "number") {
            value = this[i];
            sum += value;
        }
        else {
            value = parseFloat(value);
            sum += value;
        }
    }
    return sum;
};
Array.prototype.average = function (getField) {
    return this.sum(getField) / this.length;
};
Array.prototype.crossProduct = function (otherArray) {
    var resultset = [];
    for (var i = 0; i < this.length; i++) {
        for (var j = 0; j < otherArray.length; j++) {
            resultset.push([this[i], otherArray[j]]);
        }
    }
    return resultset;
}
if (!Array.prototype.copy) {
    Array.prototype.copy = function () {
        return this.filter(function () { return true; });
    }
}
if (!Array.prototype.exchange) {
    Array.prototype.exchange = function (i, j) {
        var temp = this[i];
        this[i] = this[j];
        this[j] = temp;
    }
}

if (!Array.prototype.sequenceEqual) {//O(n) approach
    Array.prototype.sequenceEqual = function (otherArray) {
        if (this.length !== otherArray.length) {
            return false;
        }
        for (var i = 0; i < this.length; i++) {
            if (this[i] !== otherArray[i]) {
                //console.log(sprintf("this: %d. otherArray: %d. i: %d.", this[i], otherArray[i], i));
                return false;
            }
        }
        return true;
        //set equality style, but O(n^2):  return this.every(function (e, i, A) { return A[i] === otherArray[i]; })
        //   && otherArray.every(function (e, i, A) { return A[i] === this[i]; });
    }
}

//keyFn: function that returns a primitive key field
//key: primitive value (string, numeric, bool )
Array.prototype.binarySearch = function (key, keyFn) {

    var $binarySearch = function (key, array, low, high) {
        var mid = 0;
        if (low > high) {
            return -1;
        }
        mid = Math.floor((low + high) / 2);
        if (key === keyFn(array[mid])) {
            return mid;
        }
        else if (key < keyFn(array[mid])) {
            return $binarySearch(key, array, low, mid - 1);
        }
        else {
            return $binarySearch(key, array, mid + 1, high);
        }
    };
    return $binarySearch(key, this, 0, this.length - 1);
}
